/**************************************************************************** 
 * Copyright (c) 2011 ECAlford
 * Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *      http://www.apache.org/licenses/LICENSE-2.0
 *****************************************************************************/


using System;
//using Microsoft.SPOT;
using System.IO;
//using Microsoft.SPOT.Hardware;
using System.IO.Ports;
using System.Threading;

#if MicroFramework
using Microsoft.SPOT;
#endif
//using Microsoft.SPOT.Hardware;

namespace AceBots.Xbee
{
    #region Delegates
    public delegate void XBCallback(XbeeV1FrameMgr pFrame);
    #endregion

    #region Enums
    // ApiIdentity contains possible API Identity definitions for v1 of Xbee Module
    // See http://www.sparkfun.com/datasheets/Wireless/Zigbee/XBee-Manual.pdf
    public enum ApiIdentity : byte
    {
        ModemStatus = 0x8A,
        ATCommand = 0x08,
        ATCommandQueueParameterValue = 0x09,
        ATCommandResponse = 0x88,
        RemoteCommandRequest = 0x17,
        RemoteCommandResponse = 0x97,
        TransmitRequest64 = 0x00,
        TransmitRequest16 = 0x01,
        TransmitStatus = 0x89,
        ReceiveRequest64 = 0x80,
        ReceiveRequest16 = 0x81,
        XBeeSensorReadIndicator = 0x94,
        NodeIdentificationIndicator = 0x95,
    };

    // XbeeCommands are the commands supported by this version of XbeeV1Module Class
    public enum XBeeCommand : ushort
    {
        // Special
        Write = 0x5752,                     // WR
        RestoreDefaults = 0x5245,           // RE
        SoftwareReset = 0x4652,             // FR
        NetworkReset = 0x4E52,              // NR
        // Addressing
        Address = 0x4D59,                   // MY
        SerialNumberHigh = 0x5348,          // SH
        SerialNumberLow = 0x534C,           // SL
        NodeIdentifier = 0x4E49,            // NI
        DeviceTypeIdentifier = 0x4444,      // DD
        MaximumRFPayloadBytes = 0x4E50,     // NP
        // Networking
        OperatingChannel = 0x4348,          // CH
        PanID = 0x4944,                     // ID
        NodeDiscoverTimeout = 0x4E54,       // NT
        NodeDiscover = 0x4E44,              // ND
        DestinationNode = 0x444E,           // DN
        ScanChannels = 0x5343,              // SC
        ScanDuration = 0x5344,              // SD
        // Security
        EncryptionEnable = 0x4545,          // EE
        EncryptionKey = 0x4E4B,             // NK
        LinkKey = 0x4B59,                   // KY
        // RF Interfacing
        PowerLevel = 0x504C,                // PL
        ReceivedSignalStrength = 0x4244,    // DB
        // Serial Interfacing (I/O)
        ApiEnable = 0x4150,                 // AP
        ApiOptions = 0x414F,                // AO
        InterfaceDataRate = 0x4244,         // BD
        SerialParity = 0x4E42,              // NB
        // Diagnostics
        FirmwareVersion = 0x5652,           // VR
        HardwareVersion = 0x4856,           // HV
        SupplyVoltage = 0x2556,             // %V
        // Sleep Commands
        SleepMode = 0x534D,                 // SM
        NumberOfSleepPeriods = 0x534E,      // SN
        SleepPeriod = 0x534C,               // SP
        TimeBeforeSleep = 0x5354,           // ST
        SleepOptions = 0x534F,              // SO
    };

    public enum PacketSizes : int
    { 
        ModemStatusLen = 6,
        QueryCommandLen = 30,
        DefaultMaxLen = 255,
    };
    #endregion
    public class XbeeV1Module 
    {
        /* The XbeeV1Module Class manages API communications with the
         * Version 1 Xbee modules.
         */
        #region Private Class Variables

        // Objects and variables
        private SerialPort cXbeePort;
        private XbeeV1FrameMgr cFrameMgr;
        private byte cbNextPacketID;
        private ushort csShortAddr;

        // Important event handlers
        private SerialDataReceivedEventHandler cDataRecEventHandler;
        private SerialErrorReceivedEventHandler cErrorRecEventHandler;
        #endregion

        #region Properties
        public ushort PanAddress { get; private set; }
        public uint SerialNumberLow { get; private set; }
        public uint SerialNumberHigh { get; private set; }
        public ushort ShortAddr { get{ return csShortAddr;}
            private set {csShortAddr=value;} }
        public string NodeName { get; private set; }
        public XbeeV1FrameMgr FrameManager { get { return cFrameMgr; }
            set { cFrameMgr = value; }
        }
     public AceBots.Xbee.XBCallback FrameCallback { get; set; }
        #endregion

        #region Class Create Dispose
        public XbeeV1Module(string psPortname, int piBaudRate)
        {
            cXbeePort = new SerialPort(psPortname, piBaudRate);
            cFrameMgr = new XbeeV1FrameMgr((int)AceBots.Xbee.PacketSizes.DefaultMaxLen   );
            cbNextPacketID = 1;
         
            // The call back for processing our queries
            FrameCallback = XbeeProcessCommandQuery;
            
            cDataRecEventHandler = new SerialDataReceivedEventHandler(XbeeSerial_DataReceived);
            cErrorRecEventHandler = new SerialErrorReceivedEventHandler(XbeeSerial_ErrorReceived);

            // open the port and set the event handlers
            cXbeePort.Open();
            cXbeePort.DataReceived += cDataRecEventHandler;
            cXbeePort.ErrorReceived += cErrorRecEventHandler;

            // Now Query the Xbee Module for variables
            XbeeV1FrameMgr objFrameMgr = new XbeeV1FrameMgr((int)AceBots.Xbee.PacketSizes.QueryCommandLen);
            // Get Current Info about the Xbee
            objFrameMgr.FillXbeeCommandPacket(XBeeCommand.PanID, GetNextPacketID());
            SendAPIPacket(objFrameMgr);

            objFrameMgr.FillXbeeCommandPacket(XBeeCommand.NodeIdentifier, GetNextPacketID());
            SendAPIPacket(objFrameMgr);

            objFrameMgr.FillXbeeCommandPacket(XBeeCommand.SerialNumberHigh, GetNextPacketID());
            SendAPIPacket(objFrameMgr);
  
            objFrameMgr.FillXbeeCommandPacket(XBeeCommand.SerialNumberLow, GetNextPacketID());
            SendAPIPacket(objFrameMgr);

            objFrameMgr.FillXbeeCommandPacket(XBeeCommand.Address, GetNextPacketID());
            SendAPIPacket(objFrameMgr);

            System.Threading.Thread.Sleep(2000); // This is done to allow time to process the receives from the above
                                                 // packets.

        }
        //~XbeeV1Module()
        //{
        //    Dispose();
        //}

        //void Dispose()
        //{
        //    // Makes sure we are not called multiple times
        //    if (!cblIsDisosing)
        //    {
        //        cblIsDisosing=true;
        //        if (cXbeePort != null)
        //        {
        //            cXbeePort.DataReceived -= cDataRecEventHandler;
        //            cXbeePort.ErrorReceived -= cErrorRecEventHandler;
        //            cXbeePort.Close();
        //        }

        //    }
        //}
        #endregion

        #region Public Methods
        //public bool SendCommand(XBeeCommand pCommand,ushort pValue)
        //{

        //}
     
        public byte SendAPIPacket(XbeeV1FrameMgr pFrameMgr)
        {
            // This function Sends the Api Packet contained in the pFrameMgr
 
            byte bPacketId = pFrameMgr.Buffer[(int)XbeeFrameBufferOffsets.FrameIdOffsetOrSourceAddress];
            cXbeePort.Write(pFrameMgr.Buffer, 0, pFrameMgr.FullPacketLength);
            
            return bPacketId;
        }

        #endregion
        #region Private Methods
        private void XbeeSerial_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            if (cXbeePort.BytesToRead <= 0)
            {
                // not sure why we are here, but there is nothing to do
                return;
            }

            byte[] buf = new byte[1];

            while (cXbeePort.BytesToRead > 0)
            {
                cXbeePort.Read(buf, 0, 1);
                cFrameMgr.Append(buf[0]);
                if (cFrameMgr.IsComplete)
                {
                    // got a frame, do something
                    FrameCallback(cFrameMgr);
                    // Create a thread, launch fire event, etc
                    cFrameMgr.Reset();
                }
            }
        }

        private void XbeeSerial_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
        {
            cFrameMgr.Reset();
        }

        public byte GetNextPacketID()
        {

            cbNextPacketID++;
            if (cbNextPacketID > 255)
            {
                cbNextPacketID = 2;
            }
            return cbNextPacketID;

        }

        private void XbeeProcessCommandQuery(XbeeV1FrameMgr pPacketMgr)
        {
            XBeeCommand command;
            // only processes packets that are commands and that have parameter values
            // If you set a parameter using a command, it will not return the value and 
            // packet length of receieved data will only be 5 bytes long.
            if ((pPacketMgr.ApiID == Xbee.ApiIdentity.ATCommandResponse) &&
                      (pPacketMgr.ReceivedPacketLength>5))
            {
                command = pPacketMgr.CurrentCommand();
                switch (command)
                {
                    case XBeeCommand.PanID:
                        PanAddress = pPacketMgr.UShortCommandParameter();
                        break;
                    case XBeeCommand.NodeIdentifier:
                        NodeName = pPacketMgr.StringCommandParameter();
                        break;
                    case XBeeCommand.Address:
                        ShortAddr = pPacketMgr.UShortCommandParameter();
                        break;
                    case XBeeCommand.SerialNumberLow:
                        SerialNumberLow = pPacketMgr.UintCommandParameter();
                        break;
                    case XBeeCommand.SerialNumberHigh:
                        SerialNumberHigh = (pPacketMgr.UintCommandParameter());
                        break;
                }
                pPacketMgr.Reset();

            }
        }
        
        #endregion


    }
}
